#include <stdio.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <sys/ioctl.h>

#include "window.h"
#include "_osd.h"

#ifdef __DEBUG__
#define __E(fmt, args...) fprintf(stderr, "OSD ERROR [%d]: " fmt, __LINE__, ## args)
#define __W(fmt, args...) fprintf(stderr, "OSD WARNING [%d]: " fmt, __LINE__, ## args)
#else
#define __E(fmt, args...)
#define __W(fmt, args...)
#endif

#define true        1
#define false       0

/* Device parameters */
#define OSD_DEVICE      "/dev/fb/0"
#define ATTR_DEVICE     "/dev/fb/2"

unsigned short *rgb_buffers[OSD_NUM_BUFS];
unsigned char *attr_buffers[ATTR_NUM_BUFS];
int osd_size;
int attr_size;

static int osd_fd;
static int attr_fd;

int osd_init() {
    struct fb_var_screeninfo varInfo;

    osd_fd = open(OSD_DEVICE, O_RDWR);
    if (osd_fd == -1) {
        __E("Falha ao abrir o dispositivo %s\n", OSD_DEVICE);
        return OSD_FAILURE;
    }

    if (ioctl(osd_fd, FBIOGET_VSCREENINFO, &varInfo) == -1) {
        __E("Falha de ioctl FBIOGET_VSCREENINFO em %s\n", OSD_DEVICE);
        return OSD_FAILURE;
    }

    varInfo.xres = D1_WIDTH;
    varInfo.yres = D1_HEIGHT;
    varInfo.bits_per_pixel = OSD_BITS_PER_PIXEL;

    if (ioctl(osd_fd, FBIOPUT_VSCREENINFO, &varInfo) == -1) {
        __E("Falha de ioctl FBIOPUT_VSCREENINFO em %s\n", OSD_DEVICE);
        return OSD_FAILURE;
    }

    if (varInfo.xres != D1_WIDTH || varInfo.yres != D1_HEIGHT || varInfo.bits_per_pixel != OSD_BITS_PER_PIXEL) {
        __E("Falha ao obter o tamanho da tela: %dx%d com %d bpp\n", D1_WIDTH, D1_HEIGHT, OSD_BITS_PER_PIXEL);
        return OSD_FAILURE;
    }

    osd_size = varInfo.xres * varInfo.yres;
    rgb_buffers[0] = (unsigned short *) mmap(NULL, osd_size * OSD_NUM_BUFS * OSD_BITS_PER_PIXEL / 8, PROT_READ | PROT_WRITE, MAP_SHARED, osd_fd, 0);
    if (rgb_buffers[0] == MAP_FAILED) {
        __E("Falha de mmap em %s\n", OSD_DEVICE);
        return OSD_FAILURE;
    }

    rgb_buffers[1] = rgb_buffers[0] + osd_size;
    memset(rgb_buffers[0], 0x00, osd_size * OSD_BITS_PER_PIXEL / 8 * OSD_NUM_BUFS);
    osd_setActual_rgbBuffer(0);

    return OSD_SUCCESS;
}

/* Seta a transparencia dos buffers de atributo */
void osd_setTransparency(unsigned char transparency) {
    memset(attr_buffers[0], transparency, attr_size / ATTR_PIXELS_PER_BYTE * ATTR_NUM_BUFS);
}

int osd_setActual_rgbBuffer(int osd_id) {
    struct fb_var_screeninfo vInfo;

    if (ioctl(osd_fd, FBIOGET_VSCREENINFO, &vInfo) == -1) {
        __E("Failed FBIOGET_VSCREENINFO\n");
        return OSD_FAILURE;
    }

    vInfo.yoffset = vInfo.yres * osd_id;
    //printf("osd_setbuffer --> osd_id: %d\n", osd_id);
    //printf("osd_setBuffer --> vInfo.yres: %d\n", vInfo.yres);
    //printf("osd_setBuffer --> vInfo.yoffset: %d\n", vInfo.yoffset);
    if (ioctl(osd_fd, FBIOPAN_DISPLAY, &vInfo) == -1) {
        __E("Failed FBIOPAN_DISPLAY\n");
        return OSD_FAILURE;
    }

    return OSD_SUCCESS;
}

int osd_setActual_attrBuffer(int osd_id) {
    struct fb_var_screeninfo vInfo;

    if (ioctl(attr_fd, FBIOGET_VSCREENINFO, &vInfo) == -1) {
        __E("Failed FBIOGET_VSCREENINFO\n");
        return OSD_FAILURE;
    }

    vInfo.yoffset = vInfo.yres * osd_id;
    //printf("attr_setbuffer --> osd_id: %d\n", osd_id);
    //printf("attr_setBuffer --> vInfo.yres: %d\n", vInfo.yres);
    //printf("attr_setBuffer --> vInfo.yoffset: %d\n", vInfo.yoffset);
    if (ioctl(attr_fd, FBIOPAN_DISPLAY, &vInfo) == -1) {
        __E("Failed FBIOPAN_DISPLAY\n");
        return OSD_FAILURE;
    }

    return OSD_SUCCESS;
}

int osd_exit() {
    if (osd_setActual_rgbBuffer(0) == OSD_FAILURE)
        return OSD_FAILURE;
    if (close(osd_fd) == -1)
        return OSD_FAILURE;

    return OSD_SUCCESS;
}

int attr_init() {
    struct fb_var_screeninfo vInfo;

    attr_fd = open(ATTR_DEVICE, O_RDWR);
    if (attr_fd == -1) {
        __E("Falha ao abrir o buffer de atributo %s\n", ATTR_DEVICE);
        return OSD_FAILURE;
    }

    if (ioctl(attr_fd, FBIOGET_VSCREENINFO, &vInfo) == -1) {
        __E("Erro ao ler informacoes do buffer.\n");
        return OSD_FAILURE;
    }

    // testar informações retornadas pelo FBIOGET_VSCREENINFO

    attr_size = vInfo.xres_virtual * vInfo.yres;
    attr_buffers[0] = (unsigned char *) mmap(NULL, attr_size * ATTR_NUM_BUFS / ATTR_PIXELS_PER_BYTE, PROT_READ | PROT_WRITE, MAP_SHARED, attr_fd, 0);
    if (attr_buffers[0] == MAP_FAILED) {
        __E("Falha de mmap em %s\n", ATTR_DEVICE);
        return OSD_FAILURE;
    }

    attr_buffers[1] = attr_buffers[0] + attr_size / ATTR_PIXELS_PER_BYTE;
    memset(attr_buffers[0], NO_TRANSPARENCY, attr_size * ATTR_NUM_BUFS / ATTR_PIXELS_PER_BYTE);
    osd_setActual_attrBuffer(0);

    return OSD_SUCCESS;
}

int attr_exit() {
    if (osd_setActual_attrBuffer(0) == OSD_FAILURE)
        return OSD_FAILURE;
    if (close(attr_fd) == -1)
        return OSD_FAILURE;

    return OSD_SUCCESS;
}
